<template>
    <div class="signalr">
        <div class="header">
            <div class="title">SignalR 实时消息</div>
            <div class="status" :class="statusClass">{{ statusText }}</div>
        </div>

        <div class="config">
            <label class="row">
                <span>后端地址</span>
                <input v-model="baseUrl" class="input" placeholder="http://localhost:5201" />
            </label>
            <label class="row">
                <span>Hub 路径</span>
                <input v-model="hubPath" class="input" placeholder="/chatHub" />
            </label>
            <div class="actions">
                <button class="btn" :disabled="connected" @click="connect">连接</button>
                <button class="btn" :disabled="!connected" @click="disconnect">断开</button>
                <label class="row small">
                    <input type="checkbox" v-model="autoReconnect" /> 自动重连
                </label>
            </div>
        </div>

        <div class="send">
            <input v-model="user" class="input" placeholder="当前登录用户" disabled readonly title="自动填充当前登录用户" />
            <input v-model="message" class="input" placeholder="输入要发送的消息" @keyup.enter="send" />
            <button class="btn primary" :disabled="!connected || !message.trim()" @click="send">发送</button>
        </div>

        <div class="panes three">
            <div class="pane">
                <div class="pane-title">车辆占比</div>
                <div class="echart" ref="vehiclePieEl"></div>
                <div class="stat">共 {{ counts.vehicles }} 辆</div>
            </div>
            <div class="pane">
                <div class="pane-title">货主占比</div>
                <div class="echart" ref="ownerPieEl"></div>
                <div class="stat">共 {{ counts.owners }} 位</div>
            </div>
            <div class="pane">
                <div class="pane-title">外协单位占比</div>
                <div class="echart" ref="externalPieEl"></div>
                <div class="stat">共 {{ counts.externals }} 家</div>
            </div>
        </div>

        <div class="panes">
            <div class="pane">
                <div class="pane-title">消息</div>
                <div class="list" ref="msgListEl">
                    <div v-for="(m, i) in messages" :key="i" class="msg">
                        <span class="time">{{ m.time }}</span>
                        <span class="from">{{ m.from }}</span>
                        <span class="content">{{ m.text }}</span>
                    </div>
                </div>
            </div>
            <div class="pane">
                <div class="pane-title">日志</div>
                <div class="list log" ref="logListEl">
                    <div v-for="(l, i) in logs" :key="i" class="log-item">{{ l }}</div>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, nextTick, onBeforeUnmount, onMounted } from 'vue'
import * as signalR from '@microsoft/signalr'
import { read } from '@/assets/Axios/axiosHttple'
import { useCounterStore } from '@/stores/counter'

const defaultBase = (import.meta as any).env?.VITE_SIGNALR_URL ?? 'http://localhost:5201'
const baseUrl = ref<string>(defaultBase)
const hubPath = ref<string>('/chatHub')
const autoReconnect = ref<boolean>(true)

const connection = ref<signalR.HubConnection | null>(null)
const connected = ref<boolean>(false)

const user = ref('')
const message = ref('')
const messages = ref<{ time: string; from: string; text: string }[]>([])
const logs = ref<string[]>([])
const msgListEl = ref<HTMLElement | null>(null)
const logListEl = ref<HTMLElement | null>(null)
const vehiclePieEl = ref<HTMLElement | null>(null)
const ownerPieEl = ref<HTMLElement | null>(null)
const externalPieEl = ref<HTMLElement | null>(null)
let vehiclePieChart: any = null
let ownerPieChart: any = null
let externalPieChart: any = null
const counts = ref({ vehicles: 0, owners: 0, externals: 0 })
let echartsLib: any = null

function loadScript(src: string) {
    return new Promise<void>((resolve, reject) => {
        const s = document.createElement('script')
        s.src = src
        s.onload = () => resolve()
        s.onerror = (e) => reject(e)
        document.head.appendChild(s)
    })
}

async function ensureEcharts() {
    if (echartsLib) return echartsLib
    try {
        // 尝试动态加载本地依赖
        // 注意：为避免 Vite 在 dev 阶段进行 import-analysis 报错，
        // 使用变量作为模块名并配合 @vite-ignore，失败后再回退到 CDN。
        const spec: any = 'echarts'
        const mod: any = await import(/* @vite-ignore */(spec))
        echartsLib = mod?.default ?? mod
    } catch (e) {
        // 本地未安装时，回退到 CDN
        await loadScript('https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js')
        echartsLib = (window as any).echarts
    }
    return echartsLib
}

const endpoint = computed(() => `${baseUrl.value.replace(/\/$/, '')}${hubPath.value.startsWith('/') ? '' : '/'}${hubPath.value}`)
const statusText = computed(() => (connected.value ? '已连接' : '未连接'))
const statusClass = computed(() => (connected.value ? 'ok' : 'off'))

function log(line: string) {
    logs.value.push(`${new Date().toLocaleTimeString()} ${line}`)
    void nextTick(() => {
        if (logListEl.value) logListEl.value.scrollTop = logListEl.value.scrollHeight
    })
}

function onReceiveMessage(from: string, text: string) {
    messages.value.push({
        time: new Date().toLocaleTimeString(),
        from: from || 'server',
        text: text ?? ''
    })
    void nextTick(() => {
        if (msgListEl.value) msgListEl.value.scrollTop = msgListEl.value.scrollHeight
    })
}

function onServerMessage(payload: any) {
    try {
        if (typeof payload === 'string') {
            onReceiveMessage('server', payload)
        } else {
            onReceiveMessage('server', JSON.stringify(payload))
        }
    } catch {
        onReceiveMessage('server', String(payload))
    }
}

async function connect() {
    if (connected.value) return
    const store = useCounterStore()
    const token = (store as any)?.Logins?.token
    const urlTrim = (s: string) => s.replace(/\/$/, '')
    const hubCandidates = [
        urlTrim(endpoint.value),
        `${urlTrim(read.defaults.baseURL as unknown as string)}contractmanagementread/chatHub`,
        `${urlTrim(read.defaults.baseURL as unknown as string)}baseinforead/chatHub`
    ]

    // 尝试两种模式：标准协商 + 直连 WebSockets（跳过协商）
    async function tryBuild(url: string, skipNegotiation = false) {
        const builder = new signalR.HubConnectionBuilder()
            .withUrl(url, {
                withCredentials: false,
                accessTokenFactory: token ? (() => token) : undefined,
                skipNegotiation,
                transport: skipNegotiation ? signalR.HttpTransportType.WebSockets : undefined
            })
            .configureLogging(signalR.LogLevel.Information)
        if (autoReconnect.value) builder.withAutomaticReconnect()
        return builder.build()
    }

    for (const url of hubCandidates) {
        for (const mode of [false, true]) {
            try {
                const conn = await tryBuild(url, mode)
                connection.value = conn
                conn.on('SendMessage', (payload: any) => onServerMessage(payload))
                conn.on('baseinfo.stats', (data: any) => {
                    const vehicles = Number(data?.vehicles ?? data?.vehicleCount ?? 0)
                    const owners = Number(data?.owners ?? data?.cargoOwnerCount ?? 0)
                    const externals = Number(data?.externals ?? data?.externalUnitCount ?? 0)
                    counts.value = { vehicles, owners, externals }
                    renderAllPies()
                })
                conn.onclose(err => {
                    connected.value = false
                    log(`连接已关闭${err ? ': ' + err.message : ''}`)
                })
                await conn.start()
                connected.value = true
                log(`连接成功: ${url}${mode ? ' (WebSockets直连)' : ''}`)
                try { await conn.invoke('RequestBaseInfoStats') } catch { }
                return
            } catch (e: any) {
                log(`连接尝试失败: ${url}${mode ? ' (WS)' : ''} - ${e?.message || e}`)
            }
        }
    }
    log('所有候选地址均连接失败')
}

function toArray(data: any): Array<{ name: string; value: number }> {
    if (!data) return []
    if (Array.isArray(data)) return data
    if (typeof data === 'object' && data.items) return data.items
    return []
}

function renderPie(chart: any, label: string, self: number, other: number) {
    if (!chart) return
    const total = (Number(self) || 0) + (Number(other) || 0)
    if (total <= 0) {
        chart.setOption({
            tooltip: { show: false },
            legend: { show: false },
            title: {
                text: '0',
                left: 'center',
                top: 'center',
                textStyle: { color: '#999', fontSize: 18, fontWeight: 'bold' }
            },
            series: [
                {
                    name: label,
                    type: 'pie',
                    radius: ['40%', '70%'],
                    avoidLabelOverlap: true,
                    itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
                    label: { show: false },
                    data: [
                        { name: '暂无数据', value: 1, itemStyle: { color: '#f0f2f5' } }
                    ]
                }
            ]
        })
        return
    }
    chart.setOption({
        tooltip: { trigger: 'item' },
        legend: { show: false },
        title: {
            text: String(self || 0),
            left: 'center',
            top: 'center',
            textStyle: { color: '#666', fontSize: 18, fontWeight: 'bold' }
        },
        series: [
            {
                name: label,
                type: 'pie',
                radius: ['40%', '70%'],
                avoidLabelOverlap: true,
                itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
                label: { show: false },
                data: [
                    { name: label, value: self, itemStyle: { color: '#5470C6' } },
                    { name: '其他', value: Math.max(0, other), itemStyle: { color: '#73C0DE' } }
                ]
            }
        ]
    })
}

function renderAllPies() {
    const v = counts.value
    const total = v.vehicles + v.owners + v.externals
    renderPie(vehiclePieChart, '车辆', v.vehicles, total - v.vehicles)
    renderPie(ownerPieChart, '货主', v.owners, total - v.owners)
    renderPie(externalPieChart, '外协单位', v.externals, total - v.externals)
}

onMounted(async () => {
    const echarts = await ensureEcharts()
    if (vehiclePieEl.value) vehiclePieChart = echarts.init(vehiclePieEl.value)
    if (ownerPieEl.value) ownerPieChart = echarts.init(ownerPieEl.value)
    if (externalPieEl.value) externalPieChart = echarts.init(externalPieEl.value)
    window.addEventListener('resize', () => {
        vehiclePieChart?.resize(); ownerPieChart?.resize(); externalPieChart?.resize()
    })
    // 首屏兜底：主动拉一次，防止未连接/后端暂未推送时为空白
    try { await refreshBaseInfoPie() } catch { }
})

// 初始化用户为当前登录人员（根据已有页面的存储习惯）
const initUser = (localStorage.getItem('username') || localStorage.getItem('Nickname') || localStorage.getItem('Account') || localStorage.getItem('UserName') || localStorage.getItem('UserId'))
if (initUser) user.value = String(initUser)

async function disconnect() {
    try {
        await connection.value?.stop()
        connected.value = false
        log('已断开连接')
    } catch (e: any) {
        log(`断开失败: ${e?.message || e}`)
    }
}

async function send() {
    if (!connected.value) return
    const text = message.value.trim()
    if (!text) return
    try {
        // 后端提供的方法为 ChatHub.SendAll(object message)
        const payload = { from: user.value || 'web', text, ts: new Date().toISOString() }
        await connection.value!.invoke('SendAll', payload)
        onReceiveMessage(user.value || '我', text)
        message.value = ''
    } catch (e: any) {
        log(`发送失败: ${e?.message || e}`)
    }
}

// 拉取基础信息数量并渲染饼图
async function refreshBaseInfoPie() {
    try {
        const vehicles = await fetchCount('vehicles', [
            'baseinforead/api/VehicleManagement/GetVehicleList?PageIndex=1&PageSize=1',
            'baseinforead/api/VehicleManagement/GetVehicleList?PageIndex=1&PageSize=2000'
        ])
        const owners = await fetchCount('owners', [
            'baseinforead/api/CargoOwner/GetCargoOwnerList?PageIndex=1&PageSize=1',
            'baseinforead/api/CargoOwner/GetCargoOwnerList?PageIndex=1&PageSize=2000'
        ])
        const externals = await fetchCount('externals', [
            'baseinforead/api/External/GetExternalList?PageIndex=1&PageSize=1',
            'baseinforead/api/External/GetExternalList?PageIndex=1&PageSize=2000'
        ])
        counts.value = { vehicles, owners, externals }
        renderAllPies()
    } catch (e) {
        log('获取基础信息数量失败')
    }
}

function safeParse(t: string) { try { return JSON.parse(t) } catch { return {} } }

async function fetchCount(key: 'vehicles' | 'owners' | 'externals', candidates: string[]): Promise<number> {
    for (const url of candidates) {
        try {
            const res = await read.get(url)
            if (res?.status !== 200) continue
            const j: any = res?.data ?? {}
            const n = Number(j?.data?.TotalCount ?? j?.data?.totalCount ?? j?.totalCount)
            if (!Number.isNaN(n)) return n
            const arr = Array.isArray(j?.data) ? j.data : (Array.isArray(j) ? j : null)
            if (arr) return arr.length
        } catch (e) {
            // ignore
        }
    }
    return 0
}

watch([baseUrl, hubPath], () => {
    if (connected.value) {
        // 地址改变时重连
        void disconnect().then(connect)
    }
})

onBeforeUnmount(() => {
    void disconnect()
})
</script>

<style scoped>
.signalr {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 12px;
}

.header {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.title {
    font-weight: 600;
}

.status {
    font-size: 12px;
    padding: 2px 8px;
    border-radius: 10px;
}

.status.ok {
    background: #e7f7ed;
    color: #0a8a45;
}

.status.off {
    background: #fdecea;
    color: #c62828;
}

.config {
    display: grid;
    grid-template-columns: 1fr;
    gap: 8px;
}

.row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.row>span {
    width: 80px;
    color: #666;
}

.row.small {
    font-size: 12px;
    gap: 6px;
}

.input {
    flex: 1;
    border: 1px solid #ddd;
    border-radius: 6px;
    padding: 6px 8px;
}

.actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

.btn {
    padding: 6px 12px;
    border: 1px solid #ccc;
    background: #fff;
    border-radius: 6px;
    cursor: pointer;
}

.btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.btn.primary {
    background: #4f7cff;
    color: #fff;
    border-color: #4f7cff;
}

.send {
    display: flex;
    align-items: center;
    gap: 8px;
}

.panes {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.panes.three {
    grid-template-columns: repeat(3, 1fr);
}

.pane {
    border: 1px solid #eee;
    border-radius: 8px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.pane-title {
    padding: 8px 10px;
    background: #fafafa;
    border-bottom: 1px solid #eee;
    font-weight: 600;
}

.echart {
    height: 240px;
}

.stat {
    padding: 8px 12px;
    color: #666;
}

.list {
    flex: 1;
    overflow: auto;
    padding: 10px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.msg {
    display: grid;
    grid-template-columns: 80px 80px 1fr;
    gap: 8px;
    align-items: center;
    font-size: 13px;
}

.msg .time {
    color: #999;
}

.msg .from {
    color: #4f7cff;
}

.log {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
    font-size: 12px;
    color: #666;
}

.log-item {
    white-space: pre-wrap;
    word-break: break-word;
}

@media (max-width: 900px) {
    .panes {
        grid-template-columns: 1fr;
    }
}
</style>
